home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 2007 December
/
PCWKCD1207B.iso
/
Blogowanie poza sfera
/
Flock 1.0 beta
/
flock-1.0RC3.en-US.win32.exe
/
flock
/
components
/
flockFacebookService.js
< prev
next >
Wrap
Text File
|
2007-10-18
|
71KB
|
2,095 lines
// BEGIN FLOCK GPL
//
// Copyright Flock Inc. 2005-2007
// http://flock.com
//
// This file may be used under the terms of of the
// GNU General Public License Version 2 or later (the "GPL"),
// http://www.gnu.org/licenses/gpl.html
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
// for the specific language governing rights and limitations under the
// License.
//
// END FLOCK GPL
const CC = Components.classes;
const CI = Components.interfaces;
const CR = Components.results;
Components.utils.import("resource://gre/modules/JSON.jsm");
const FACEBOOK_CID = Components.ID("{81a58e10-4f45-11db-b0de-0800200c9a66}");
const FACEBOOK_CONTRACTID = "@flock.com/people/facebook;1";
const FACEBOOK_FAVICON = "http://www.facebook.com/favicon.ico";
const FACEBOOK_TITLE = "Facebook Web Service";
const SERVICE_ENABLED_PREF = "flock.service.facebook.enabled";
const CATEGORY_COMPONENT_NAME = "Facebook JS Component";
const CATEGORY_ENTRY_NAME = "facebook";
const FLOCK_RICH_CONTENT_CATEGORY_ENTRY = "flockRichContentHandler";
const XPATH_WALLTEXT = "wallTextXPath";
const FLOCK_ACCOUNT_UTILS_CONTRACTID = "@flock.com/account-utils;1";
const FLOCK_ERROR_CONTRACTID = "@flock.com/error;1";
const FLOCK_PHOTO_ACCOUNT_CONTRACTID = "@flock.com/photo-account;1";
const FLOCK_PHOTO_ALBUM_CONTRACTID = "@flock.com/photo-album;1";
const FLOCK_PHOTO_API_MANAGER_CONTRACTID = "@flock.com/photo-api-manager;1?";
const FLOCK_PHOTO_CONTRACTID = "@flock.com/photo;1";
const FLOCK_RDNDS_CONTRACTID = "@flock.com/rich-dnd-service;1";
const FLOCK_SINGLETON_CONTRACTID = "@flock.com/singleton;1";
const CATEGORY_MANAGER_CONTRACTID = "@mozilla.org/categorymanager;1";
const HASH_PROPERTY_BAG_CONTRACTID = "@mozilla.org/hash-property-bag;1";
const JS_SUBSCRIPT_LOADER_CONTRACTID = "@mozilla.org/moz/jssubscript-loader;1";
const OBSERVER_SERVICE_CONTRACTID = "@mozilla.org/observer-service;1";
const FLOCK_SNOWMAN_URL = "chrome://browser/skin/flock/services/common/no_avatar.png";
const FAVES_COOP = "chrome://flock/content/common/load-faves-coop.js";
const FACEBOOK_PROPERTIES = "chrome://flock/locale/services/facebook.properties";
const PEOPLE_PROPERTIES = "chrome://flock/locale/people/people.properties";
const FACEBOOK_IDENTITY_URN_PREFIX = "urn:flock:identity:facebook:";
const FACEBOOK_SHARE_FLOCK_SUBJECT = "flock.facebook.friendShareFlock.subject";
const FACEBOOK_SHARE_FLOCK_MESSAGE = "flock.facebook.friendShareFlock.message";
// The first time, only get photos not older than one week
const MEDIA_INITIAL_FETCH = 7 * 24 * 3600;
// The delay between two refreshes when the sidebar is closed (in seconds)
const FACEBOOK_REFRESH_INTERVAL = 1800; // 30 minutes
// The delay between two refreshes when the sidebar is open (in seconds)
const FACEBOOK_SHORT_INTERVAL = 300; // 5 minutes
// CONSTANTS FOR PHOTO UPLOADING
// XXX This is a rough guess. Facebook allows a max of 60 pictures per
// personal account, at a max file size of 5MB each. Once there are API
// calls to actually get the space remaining, we should replace this.
const FACEBOOK_MAX_PHOTO_SPACE = 60 * 5 * 1024 * 1024;
const FACEBOOK_USED_PHOTO_SPACE = 0;
// Facebook has a maximum file size of 5MB
const FACEBOOK_MAX_FILE_SIZE = 5 * 1024 * 1024;
const CHAR_CODE_SPACE = " ".charCodeAt(0);
// ====================================================
// ========== BEGIN General helper functions ==========
// ====================================================
var gCompTK;
function getCompTK() {
if (!gCompTK) {
gCompTK = CC[FLOCK_SINGLETON_CONTRACTID]
.getService(CI.flockISingleton)
.getSingleton("chrome://browser/content/flock/services/common/load-compTK.js")
.wrappedJSObject;
}
return gCompTK;
}
// For use with the scheduler
var gTimers = [];
// String defaults... may be updated later through Web Detective
var gStrings = {
"domains": "facebook.com",
"userlogin": "http://www.facebook.com/login.php",
"userprofile": "http://www.facebook.com/profile.php?id=%accountid%"
};
var _wm = CC["@mozilla.org/appshell/window-mediator;1"]
.getService(CI.nsIWindowMediator);
function loadSubScript(spec) {
var loader = CC[JS_SUBSCRIPT_LOADER_CONTRACTID]
.getService(CI.mozIJSSubScriptLoader);
var context = {};
loader.loadSubScript(spec, context);
return context;
}
function loadLibraryFromSpec(aSpec) {
var loader = CC[JS_SUBSCRIPT_LOADER_CONTRACTID]
.getService(CI.mozIJSSubScriptLoader);
loader.loadSubScript(aSpec);
}
loadLibraryFromSpec("chrome://browser/content/flock/photo/photoAPI.js");
loadLibraryFromSpec("chrome://flock/content/common/flocksafe.js");
function _getIdentityUrn(aAccountId, aUid) {
var result = FACEBOOK_IDENTITY_URN_PREFIX
+ aAccountId + ":"
+ aUid;
return result;
}
// A special hack to trigger embedding rich content into a Facebook field.
// Added as a general helper function as it is used in both FacebookService
// and FacebookAccount classes.
function _initKeyupEvent(aTarget) {
if (!aTarget) {
return;
}
var doc = aTarget.ownerDocument;
if (!(doc instanceof CI.nsIDOMHTMLDocument)) {
return;
}
var docView = doc.QueryInterface(CI.nsIDOMDocumentView).defaultView;
if (!(docView instanceof CI.nsIDOMAbstractView)) {
return;
}
// add an extra space as the first of two parts to simulate the keyup event
aTarget.value += " ";
// Send a key-up event to the field: this triggers a Facebook
// process which scrapes the field for a link which, if found,
// will embed the rich content into the field.
var eventObj = doc.createEvent("KeyEvents");
eventObj.initKeyEvent("keyup", true, true, docView, false, false, false,
false, CI.nsIDOMKeyEvent.DOM_VK_SPACE, CHAR_CODE_SPACE);
aTarget.dispatchEvent(eventObj);
// Remove the space to get back to the original value
var val = aTarget.value;
aTarget.value = val.substring(0, val.length - 1);
}
// Appends the Flock breadcrumb to the given text field
function _addBreadcrumb(aField) {
if (aField) {
var breadcrumb = CC[FLOCK_RDNDS_CONTRACTID]
.getService(CI.flockIRichDNDService)
.getBreadcrumb("plain");
if (breadcrumb) {
aField.value += breadcrumb;
}
}
}
//******************************************
//* Service Class
//******************************************
function flockFacebookService() {
this.obs = CC[OBSERVER_SERVICE_CONTRACTID].getService(CI.nsIObserverService);
this.acUtils = CC[FLOCK_ACCOUNT_UTILS_CONTRACTID].
getService(CI.flockIAccountUtils);
this.status = CI.flockIWebService.STATUS_UNKNOWN;
this.url = "http://www.facebook.com";
this.api = CC["@flock.com/api/facebook;1"].getService(CI.flockIFacebookAPI).wrappedJSObject;
this.mIsInitialized = false;
this.photoList = null;
this._ctk = {
interfaces: [
"nsISupports",
"nsIClassInfo",
"nsISupportsCString",
"nsIObserver",
"flockIWebService",
"flockIAuthenticateNewAccount",
"flockISocialWebService",
"flockIManageableWebService",
"flockIPollingService",
"flockIMediaWebService",
"flockIRichContentDropHandler"
],
shortName: "facebook",
fullName: "Facebook",
description: FACEBOOK_TITLE,
domains: gStrings["domains"],
favicon: FACEBOOK_FAVICON,
CID: FACEBOOK_CID,
contractID: FACEBOOK_CONTRACTID,
accountClass: flockFacebookAccount,
needPassword: true
};
var sbs = CC["@mozilla.org/intl/stringbundle;1"]
.getService(CI.nsIStringBundleService);
var bundle = sbs.createBundle(FACEBOOK_PROPERTIES);
this._channels = {
"special:photosFromFriends": {
title: bundle.GetStringFromName("flock.facebook.title.friends"),
supportsSearch: false
},
"special:photosOfFriends": {
title: bundle.GetStringFromName("flock.facebook.title.recent"),
supportsSearch: false
},
"special:photosOfYou": {
title: bundle.GetStringFromName("flock.facebook.title.you"),
supportsSearch: false
}
};
this._logger = CC["@flock.com/logger;1"].createInstance(CI.flockILogger);
this._logger.init("facebookService");
this._profiler = CC["@flock.com/profiler;1"].getService(CI.flockIProfiler);
this.init();
}
flockFacebookService.prototype.init =
function flockFacebookService_init() {
// Prevent re-entry
if (this.mIsInitialized) {
return;
}
this.mIsInitialized = true;
this._logger.info(".init()");
// Determine whether this service has been disabled, and unregister if so.
this.prefService = CC["@mozilla.org/preferences-service;1"]
.getService(CI.nsIPrefBranch);
if (this.prefService.getPrefType(SERVICE_ENABLED_PREF) &&
!this.prefService.getBoolPref(SERVICE_ENABLED_PREF)) {
this._logger.info(SERVICE_ENABLED_PREF + " is FALSE! Not initializing.");
var cm = CC[CATEGORY_MANAGER_CONTRACTID].getService(CI.nsICategoryManager);
cm.deleteCategoryEntry("wsm-startup", CATEGORY_COMPONENT_NAME, true);
cm.deleteCategoryEntry("flockWebService", CATEGORY_ENTRY_NAME, true);
cm.deleteCategoryEntry("flockMediaProvider", CATEGORY_ENTRY_NAME, true);
cm.deleteCategoryEntry(FLOCK_RICH_CONTENT_CATEGORY_ENTRY,
CATEGORY_ENTRY_NAME, true);
return;
}
this.faves_coop = CC[FLOCK_SINGLETON_CONTRACTID]
.getService(CI.flockISingleton)
.getSingleton(FAVES_COOP)
.wrappedJSObject;
this.account_root = this.faves_coop.accounts_root;
this.fbService = new this.faves_coop.Service(
"urn:facebook:service", {
name: "facebook",
desc: "Facebook"
});
this.fbService.serviceId = FACEBOOK_CONTRACTID;
this.urn = this.fbService.id();
// Load Web Detective file
this.webDetective = this.acUtils.useWebDetective("facebook.xml");
for (var s in gStrings) {
gStrings[s] = this.webDetective.getString("facebook", s, gStrings[s]);
}
this.fbService.domains = gStrings["domains"];
this.fbService.loginURL = gStrings["userlogin"];
// Update auth states
try {
if (this.webDetective.detectCookies("facebook", "loggedout", null)) {
this.acUtils.markAllAccountsAsLoggedOut(FACEBOOK_CONTRACTID);
this.logout();
}
} catch (ex) {
this._logger.error("ERROR updating auth states for Facebook: " + ex);
}
// On browser restart if a Facebook coop account is still marked as
// authenticated we also need to reauth the api. We do this by calling
// login() on the xpcom account obj. However, we have to wait while
// the Facebook service is finished constructing itself.
var inst = this;
var reloginCallback = {
notify: function reloginCallback_notify(aTimer) {
inst._logger.debug("reloginCallback.notify()");
var acctURN = inst.acUtils
.getFirstAuthenticatedAccountForService(FACEBOOK_CONTRACTID);
if (acctURN) {
var account = inst.getAccount(acctURN);
if (account) {
var reloginListener = {
onSuccess: function reloginListener_onSuccess(aSubject, aTopic) {
inst._logger.debug(".init(): reloginListener: onSuccess()");
},
onError: function reloginListener_onError(aError) {
inst._logger.debug(".init(): reloginListener: onError()");
inst.faves_coop.get(acctURN).isAuthenticated = false;
}
};
account.login(reloginListener);
}
}
}
};
var reloginTimer = CC["@mozilla.org/timer;1"].createInstance(CI.nsITimer);
reloginTimer.initWithCallback(reloginCallback, 100, CI.nsITimer.TYPE_ONE_SHOT);
}
/**
* The following method defined in nsIObserver are provided by component
* toolkit:
*
* @see nsIObserver#observe
*/
/**
* @see flockIPollingService#refresh
*/
flockFacebookService.prototype.refresh =
function flockFacebookService_refresh(aUrn, aListener) {
this._logger.debug(".refresh() with aUrn of " + aUrn);
var refreshItem = this.faves_coop.get(aUrn);
if (refreshItem instanceof this.faves_coop.Account) {
this._logger.debug("refreshing an Account");
if (this.api.isLoggedIn) {
this._refreshAccount(aUrn, aListener);
} else {
this._logger.debug("api is not logged in - skipping refresh");
// If the user is not logged in, return a success without
// refreshing anything
aListener.onResult();
}
} else if (refreshItem instanceof this.faves_coop.Identity) {
this._logger.debug("refreshing an Identity");
aListener.onResult();
} else {
throw CR.NS_ERROR_ABORT;
this._logger.error("can't refresh " + aUrn + " (unsupported type)");
aListener.onError(null);
}
}
flockFacebookService.prototype._refreshAccount =
function flockFacebookService_refreshAccount(aUrn, aListener) {
var inst = this;
var refreshItem = this.faves_coop.get(aUrn);
var lastUpdate = refreshItem.lastUpdateDate;
var peopleHash;
var numberOfAnswers = 0;
function onIndividualSuccess() {
numberOfAnswers++;
if (numberOfAnswers >= 3) {
if (inst.acUtils.isPeopleSidebarOpen()) {
refreshItem.nextRefresh = new Date(Date.now()
+ FACEBOOK_SHORT_INTERVAL * 1000);
}
if (aListener) {
aListener.onResult();
}
}
}
var mediaFriendsListener = {
onSuccess: function mfl_onSuccess(aEnum, aStatus) {
inst._logger.info("Successful getUpdatedMediaFriends");
aEnum.QueryInterface(CI.nsISimpleEnumerator);
// Get the media information, put it in the peopleHash
while (aEnum.hasMoreElements()) {
var bag = aEnum.getNext();
bag.QueryInterface(CI.nsIPropertyBag);
uid = bag.getProperty("uid");
peopleHash[uid].media = {
count: bag.getProperty("count"),
latest: bag.getProperty("latest")
}
}
// Now that we have all we need, update the RDF
function myWorker(aShouldYield) {
var count = 0;
// ADD or update existing people
for (uid in peopleHash) {
count++;
inst._addPerson(peopleHash[uid], refreshItem);
if (aShouldYield()) {
yield;
}
}
// REMOVE locally people removed on the server
var localEnum = refreshItem.friendsList.children.enumerate();
while (localEnum.hasMoreElements()) {
var identity = localEnum.getNext();
if (!peopleHash[identity.accountId]) {
inst._logger.info("Friend " + identity.accountId
+ " has been deleted on the server");
refreshItem.friendsList.children.remove(identity);
identity.destroy();
}
}
onIndividualSuccess();
}
getCompTK().schedule(gTimers, 0.05, 10, myWorker);
},
onError: function mfl_onError(aError) {
inst._logger.error("Error on getUpdatedMediaFriends");
}
}
var friendsGetListener = {
onSuccess: function fgl_onSuccess(aResult, aStatus) {
inst._logger.info("Success for friendsGetListener");
peopleHash = aResult.wrappedJS;
refreshItem.lastUpdateDate = new Date();
var lastUpdateStr;
if (lastUpdate) {
// Convert milliseconds to seconds then to a string
lastUpdateStr = new String(parseInt(lastUpdate.getTime() / 1000));
} else {
lastUpdateStr = new String(parseInt(Date.now() / 1000)
- MEDIA_INITIAL_FETCH);
}
inst.api.getUpdatedMediaFriends(mediaFriendsListener, lastUpdateStr);
},
onError: function fgl_onError() {
inst._logger.error("Error on gettingFriends")
}
};
var usersGetInfoListener = {
onSuccess: function ugil_onSuccess(aEnum, aStatus) {
inst._logger.info("Success for usersGetInfo");
aEnum.QueryInterface(CI.nsISimpleEnumerator);
if (aEnum.hasMoreElements()) {
var bag = aEnum.getNext();
bag.QueryInterface(CI.nsIPropertyBag);
refreshItem.avatar = bag.getProperty("avatar");
// We get the full name each refresh since it's possible for the
// user's full name to change.
refreshItem.name = bag.getProperty("name");
refreshItem.statusMessage = bag.getProperty("statusMessage");
refreshItem.lastProfileUpdate = bag.getProperty("lastProfileUpdate");
onIndividualSuccess();
}
},
onError: function ugil_onError() {
inst._logger.error("Error on usersGetInfo");
}
};
var notificationsGetListener = {
onSuccess: function ngl_onSuccess(aEnum, aStatus) {
inst._logger.info("Success for notificatonsGet");
aEnum.QueryInterface(CI.nsISimpleEnumerator);
if (aEnum.hasMoreElements()) {
var bag = aEnum.getNext();
bag.QueryInterface(CI.nsIPropertyBag);
// if any of the Me notifications have increased in count,
// light the people button
if ((refreshItem.accountMessages < bag.getProperty("messages")) ||
(refreshItem.fbPokes < bag.getProperty("pokes")) ||
(refreshItem.fbFriendRequests < bag.getProperty("friendRequests")) ||
(refreshItem.fbGroupInvites < bag.getProperty("groupInvites")) ||
(refreshItem.fbEventInvites < bag.getProperty("eventInvites")))
{
inst._lightPeopleIcon();
}
// write notifications to RDF
refreshItem.accountMessages = bag.getProperty("messages");
refreshItem.fbPokes = bag.getProperty("pokes");
refreshItem.fbFriendRequests = bag.getProperty("friendRequests");
refreshItem.fbGroupInvites = bag.getProperty("groupInvites");
refreshItem.fbEventInvites = bag.getProperty("eventInvites");
}
onIndividualSuccess();
},
onError: function ngl_onError() {
inst._logger.error("Error on notificatonsGet");
}
};
this.api.usersGetInfo(usersGetInfoListener,
this.api.uid,
["name,pic_square,status"]);
this.api.friendsGet(friendsGetListener);
this.api.notificationsGet(notificationsGetListener);
}
flockFacebookService.prototype._incrementMedia =
function flockFacebookService_incrementMedia(aUid, aCount, aLatest) {
var currAcctURN = this.acUtils.getFirstAuthenticatedAccountForService(FACEBOOK_CONTRACTID);
var currAcct = this.faves_coop.get(currAcctURN);
// Update the count on the identity...
var identityUrn = _getIdentityUrn(currAcct.accountId, aUid);
identity = this.faves_coop.get(identityUrn);
identity.unseenMedia += aCount;
// + 10 explaination: when the friend uploads a new photo, Facebook
// updates the profile update time a few seconds after the photo upload.
// We want that to show as a "media" update, not as a "profile" update
if ((aLatest + 10) >= identity.lastUpdate) {
identity.lastUpdate = aLatest;
identity.lastUpdateType = "media";
}
}
flockFacebookService.prototype._addPerson =
function flockFacebookService_addPerson(aPerson, aCoopAccount) {
var person = aPerson;
// We include the current accountId in the identity urn to prevent friend
// collisions if multiple Facebook accounts have the same friend.
var identityUrn = _getIdentityUrn(aCoopAccount.accountId,
person.uid);
var updating = this.faves_coop.Identity.exists(identityUrn);
var identity;
var lastProfileUpdate = parseInt(person.profile_update_time, 10);
var statusTime = parseInt(person.status.time, 10);
var lastUpdate = 0;
var lastUpdateType;
if (statusTime > lastProfileUpdate) {
lastUpdate = statusTime;
lastUpdateType = "status";
} else {
lastUpdate = lastProfileUpdate;
lastUpdateType = "profile";
}
this._logger.debug("Adding person: " + person.uid + " - " + person.name);
if (updating) {
// update data of the identity coop obj here
identity = this.faves_coop.get(identityUrn);
if (lastUpdate > identity.lastUpdate) {
identity.lastUpdate = lastUpdate;
identity.lastUpdateType = lastUpdateType;
identity.name = person.name;
identity.avatar = person.pic_square;
identity.statusMessage = person.status.message;
}
} else {
identity = new this.faves_coop.Identity(
identityUrn,
{
name: person.name,
serviceId: FACEBOOK_CONTRACTID,
accountId: person.uid,
avatar: person.pic_square,
statusMessage: person.status.message,
lastUpdate: lastUpdate,
lastUpdateType: lastUpdateType
}
);
aCoopAccount.friendsList.children.add(identity);
}
if (person.media) {
this._incrementMedia(person.uid,
person.media.count,
person.media.latest);
}
}
flockFacebookService.prototype.markAllMediaSeen =
function flockFacebookService_markAllMediaSeen(aIdentityUrn) {
var identity = this.faves_coop.get(aIdentityUrn);
identity.unseenMedia = 0;
}
// Facebook's HTML sets a maxlength of 160 on the status entry textbox.
flockFacebookService.prototype.maxStatusLength = 160;
flockFacebookService.prototype._lightPeopleIcon =
function flockFacebookService_lightPeopleIcon() {
this._logger.debug("._lightPeopleIcon()");
this.obs.notifyObservers(null, "new-people-notification", null);
}
flockFacebookService.prototype._addPhotostream =
function flockFacebookService_addPhotostream(aPhotoPerson, aCoopAccount) {
var mediaFavesUrn = "urn:media:favorites";
var query = new queryHelper();
var person = aPhotoPerson.QueryInterface(CI.nsIPropertyBag);
query.user = person.getProperty("uid");
query.username = person.getProperty("name");
this._logger.info(".addPhotostream('" + query.username + "')");
var mediaQueryUrn = mediaFavesUrn + ":facebook:" + query.stringVal;
var mediaQuery = this.faves_coop.get(mediaQueryUrn);
if (!mediaQuery) {
mediaQuery = new this.faves_coop.MediaQuery(
mediaQueryUrn,
{
serviceId: FLOCK_PHOTO_API_MANAGER_CONTRACTID,
service: this.shortName,
favicon: FACEBOOK_FAVICON,
isPollable: true,
query: query.stringVal,
name: person.getProperty("name")
}
);
}
mediaQuery.isTransient = aCoopAccount.isTransient;
var mediaFaves = this.faves_coop.get(mediaFavesUrn);
if (!mediaFaves) {
mediaFaves = new this.faves_coop.Folder(mediaFavesUrn);
this.faves_coop.favorites_root.children.add(mediaFaves);
}
mediaFaves.children.addOnce(mediaQuery);
}
/**
* The following attributes and methods defined in flockIWebService are
* provided by component toolkit:
*
* @see flockIWebService#icon
* @see flockIWebService#needPassword
* @see flockIWebService#shortName
* @see flockIWebService#title
* @see flockIWebService#urn
* @see flockIWebService#getAccount
* @see flockIWebService#getAccounts
* @see flockIWebService#removeAccount
*/
/**
* @see flockIWebService#addAccountById
*/
flockFacebookService.prototype.addAccountById =
function flockFacebookService_addAccountById(aAccountID,
aIsTransient,
aListener) {
this._logger.debug(".addAccountById('"
+ aAccountID + "', "
+ aIsTransient + "...)");
var pw = this.acUtils.getPassword(this.urn + ":" + aAccountID);
/// @todo: FIX ME - name shouldn't be email address (pw.user) - DP
var name = (pw) ? pw.user : aAccountID;
var accountUrn = "urn:flock:facebook:account:" + aAccountID;
var account = new this.faves_coop.Account(
accountUrn, {
name: name,
serviceId: FACEBOOK_CONTRACTID,
service: this.fbService,
accountId: aAccountID,
isPollable: false,
refreshInterval: FACEBOOK_REFRESH_INTERVAL,
URL: gStrings["userprofile"].replace("%accountid%", aAccountID),
isTransient: aIsTransient,
favicon: FACEBOOK_FAVICON
});
this.account_root.children.add(account);
this.USER = account.id();
var friendsListUrn = accountUrn + ":friends";
var friendsList = new this.faves_coop.FriendsList(
friendsListUrn,
{
account: account
});
account.friendsList = friendsList;
// Instantiate account component
var acct = this.getAccount(account.id());
if (aListener) {
aListener.onSuccess(acct, "addAccount");
}
return acct;
}
// BEGIN flockIAuthenticateNewAccount interface
flockFacebookService.prototype.authenticateNewAccount =
function flockFacebookService_authenticateNewAccount(aListener)
{
this._logger.info("{flockIAuthenticateNewAccount}.authenticateNewAccount(aListener)");
var wm = CC["@mozilla.org/appshell/window-mediator;1"]
.getService(CI.nsIWindowMediator);
var win = wm.getMostRecentWindow('navigator:browser');
win.openUILinkIn(this.url, "tab");
}
// END flockIAuthenticateNewAccount interface
/**
* The following methods defined in flockIManageableWebService are provided by
* component toolkit:
*
* @see flockIManageableWebService#docRepresentsSuccessfulLogin
* @see flockIManageableWebService#getAccountIDFromDocument
* @see flockIManageableWebService#getCredentialsFromForm
* @see flockIManageableWebService#ownsDocument
* @see flockIManageableWebService#ownsLoginForm
*/
/**
* @see flockIManageableWebService#updateAccountStatusFromDocument
*/
flockFacebookService.prototype.updateAccountStatusFromDocument =
function flockFacebookService_updateAccountStatusFromDocument(aDocument) {
this._logger.debug(".updateAccountStatusFromDocument(aDocument)");
if (this.webDetective.detect("facebook", "loggedout", aDocument, null)) {
this.acUtils.markAllAccountsAsLoggedOut(FACEBOOK_CONTRACTID);
this.api.deauthenticate();
} else if (this.webDetective.detect("facebook",
"loggedin",
aDocument,
null))
{
var results = CC[HASH_PROPERTY_BAG_CONTRACTID]
.createInstance(CI.nsIWritablePropertyBag2);
if (this.webDetective.detect("facebook",
"accountinfo",
aDocument,
results))
{
var acctId = results.getPropertyAsAString("accountid");
var acctUrn = this.acUtils.getAccountURNById(this.urn, acctId);
var acct = this.faves_coop.get(acctUrn);
if (!acct.isAuthenticated) {
var inst = this;
var loginListener = {
onSuccess: function loginListener_onSuccess() {
inst._logger.info(".updateAccountStatusFromDocument(): "
+ "loginListener: onSuccess()");
acct.isAuthenticated = true;
},
onError: function loginListener_onError(aError) {
inst._logger.info(".updateAccountStatusFromDocument(): "
+ "loginListener: onError()");
inst._logger.info(aError ? (aError.errorString) : "No details");
acct.isAuthenticated = false;
}
};
this.getAccount(acctUrn).login(loginListener);
}
}
}
}
/**
* createAlbum
* @see flockIMediaWebService#createAlbum
*/
flockFacebookService.prototype.createAlbum =
function flockFacebookService_createAlbum(aListener, aTitle) {
// trim white space from front and end of string
var newTitle = aTitle.replace(/^\s+|\s+$/g, "");
if (newTitle) {
var myListener = {
onSuccess: function listener_onSuccess(aAlbum, aStatus) {
aListener.onCreateAlbum(aAlbum);
},
onError: function listener_onError(aFlockError) {
aListener.onError(aFlockError);
}
}
this.api.createAlbum(myListener, newTitle);
} else {
var error = CC[FLOCK_ERROR_CONTRACTID].createInstance(CI.flockIError);
error.errorCode = CI.flockIError.PHOTOSERVICE_EMPTY_ALBUMNAME;
aListener.onError(null, null, error);
}
}
/**
* findByUsername
* @see flockIMediaWebService#findByUsername
*/
flockFacebookService.prototype.findByUsername =
function flockFacebookService_findByUsername(aListener, aUsername) {
// need to call this to trigger mediaObserver
aListener.onError(null);
}
/**
* getAccountStatus
*
* This currently returns hardcoded values so the uploader works. This
* should be replaced by actual calls to the Facebok API, or with values
* screen-scraped by Web Detective.
*
* @see flockIMediaWebService#getAccountStatus
* @todo Use the Facebook API or Web Detective to correctly obtain this
* information.
* @todo Localize the hardcoded strings.
*/
flockFacebookService.prototype.getAccountStatus =
function flockFacebookService_getAccountStatus(aListener) {
var result = CC["@mozilla.org/hash-property-bag;1"]
.createInstance(CI.nsIWritablePropertyBag2);
result.setPropertyAsAString("maxSpace", FACEBOOK_MAX_PHOTO_SPACE);
result.setPropertyAsAString("usedSpace", FACEBOOK_USED_PHOTO_SPACE);
result.setPropertyAsAString("maxFileSize", FACEBOOK_MAX_FILE_SIZE);
// This is a key, not something displayed to the user. Don't l10n this!
result.setPropertyAsAString("usageUnits", "bytes");
result.setPropertyAsBool("isPremium", true); // Facebook has no "premium" level.
aListener.onSuccess(result, "");
}
/**
* getAlbums
* @see flockIMediaWebService#getAlbums
*
* aUsername is the FB uid
*/
flockFacebookService.prototype.getAlbums =
function flockFacebookService_getAlbums(aListener, aUsername) {
this._logger.info(".getAlbums(aListener, " + aUsername + ")");
var myListener = {
onSuccess: function listener_onSuccess(aEnumResults, aStatus) {
aListener.onGetAlbumsResult(aEnumResults);
},
onError: function listener_onError(aSubject, aTopic, aFlockError) {
aListener.onError(aFlockError);
}
};
this.api.getAlbums(myListener, aUsername);
}
/**
* getAuthPerson
* @see flockIMediaWebService#getAuthPerson
*/
flockFacebookService.prototype.getAuthPerson =
function flockFacebookService_getAuthPerson () {
try {
var person = {};
person.id = this.api.uid;
// XXX Localize these
person.fullname = "Unknown";
person.username = "Unknown";
person.service = this;
return person;
} catch (ex) {
return null;
}
}
/**
* getContacts
* @see flockIMediaWebService#getContacts
* @todo Implement this, or handle the error more gracefully.
* @throws NS_ERROR_NOT_IMPLEMENTED
*/
flockFacebookService.prototype.getContacts =
function flockFacebookService_getContacts(aListener) {
throw CR.NS_ERROR_NOT_IMPLEMENTED;
}
/**
* getMostRecentPhotoForList
* @see flockIMediaWebService#getMostRecentPhotoForList
* @todo Implement this, or handle the error more gracefully.
* @throws NS_ERROR_NOT_IMPLEMENTED
*/
flockFacebookService.prototype.getMostRecentPhotoForList =
function flockFacebookService_getMostRecentPhotoForList(aListener,
aEnumerator) {
throw CR.NS_ERROR_NOT_IMPLEMENTED;
}
/**
* getPhoto
* @see flockIMediaWebService#getPhoto
* @todo Implement this, or handle the error more gracefully.
* @throws NS_ERROR_NOT_IMPLEMENTED
*/
flockFacebookService.prototype.getPhoto =
function flockFacebookService_getPhoto(aListener, aPhotoId) {
throw CR.NS_ERROR_NOT_IMPLEMENTED;
}
/**
* getPhotoFromRDFNode
* @see flockIMediaWebService#getPhotoFromRDFNode
*/
flockFacebookService.prototype.getPhotoFromRDFNode =
function flockFacebookService_getPhotoFromRDFNode(aRdfId) {
var newPhoto = this.api.createPhoto();
var coopPhoto = this.faves_coop.get(aRdfId);
newPhoto.webPageUrl = coopPhoto.URL;
newPhoto.thumbnail = coopPhoto.thumbnail;
newPhoto.midSizePhoto = coopPhoto.midSizePhoto;
newPhoto.largeSizePhoto = coopPhoto.largeSizePhoto;
newPhoto.username = coopPhoto.username;
newPhoto.userid = coopPhoto.userid;
newPhoto.title = coopPhoto.name;
newPhoto.id = coopPhoto.photoid;
newPhoto.icon = coopPhoto.favicon;
newPhoto.uploadDate = coopPhoto.datevalue;
newPhoto.is_public = coopPhoto.is_public;
newPhoto.is_video = coopPhoto.is_video;
return newPhoto;
}
/**
* logout
* @see flockIWebService#logout
*/
flockFacebookService.prototype.logout =
function flockFacebookService_logout() {
this._logger.debug(".logout()");
this.api.deauthenticate();
this.acUtils.removeCookies(this.webDetective.getSessionCookies("facebook"));
this.USER = null;
}
/**
* search
* @see flockIMediaWebService#search
*/
flockFacebookService.prototype.search =
function flockFacebookService_search(aListener, aQueryString, aCount, aPage) {
this._logger.debug("Attempting to run search query: " + aQueryString);
var inst = this;
if (aPage > 1) {
// Implement a pseudo-pagination since Facebook does not support pagination
getPhotosDetails(aCount, aPage);
return;
}
var aQuery = new queryHelper(aQueryString);
function createEnum(myarray) {
myarray.reverse();
return {
QueryInterface: function (iid) {
if (iid.equals(Components.interfaces.nsISimpleEnumerator)) {
return this;
}
throw Components.results.NS_ERROR_NO_INTERFACE;
},
hasMoreElements: function() {
return (myarray.length>0);
},
getNext: function() {
return myarray.shift();
}
}
}
function getPhotosDetails(aCount, aPage) {
var photos = [];
if (inst.photoList) {
var owners = [];
var sbs = CC["@mozilla.org/intl/stringbundle;1"].
getService(CI.nsIStringBundleService);
var bundleUrl = "chrome://flock/locale/photo/mediabar.properties";
var bundle = sbs.createBundle(bundleUrl);
var pageEnd = aCount * aPage;
if (pageEnd > inst.photoList.length) {
pageEnd = inst.photoList.length;
}
for (var i = aCount * (aPage - 1); i < pageEnd; i++) {
var photo = inst.photoList[i];
var id = photo.getElementsByTagName('pid')[0]
.firstChild
.nodeValue;
var title;
if (photo.getElementsByTagName('caption')[0].firstChild) {
title = photo.getElementsByTagName('caption')[0]
.firstChild
.nodeValue;
} else {
title = bundle.GetStringFromName("flock.media.tooltip.untitled");
}
var owner = photo.getElementsByTagName('owner')[0]
.firstChild.nodeValue;
var date_upload = photo.getElementsByTagName('created')[0]
.firstChild
.nodeValue;
var square_url = photo.getElementsByTagName('src')[0]
.firstChild
.nodeValue;
var small_url = photo.getElementsByTagName('src_small')[0]
.firstChild
.nodeValue;
var big_url = photo.getElementsByTagName('src_big')[0]
.firstChild
.nodeValue;
var page_url = photo.getElementsByTagName('link')[0]
.firstChild
.nodeValue;
var newPhoto = inst.api.createPhoto();
newPhoto.webPageUrl = page_url;
newPhoto.thumbnail = small_url;
newPhoto.midSizePhoto = square_url;
newPhoto.largeSizePhoto = big_url;
newPhoto.userid = owner;
newPhoto.title = title;
newPhoto.id = id;
newPhoto.uploadDate = parseInt(date_upload) * 1000;
photos.push(newPhoto);
owners.push(owner);
}
if (pageEnd == inst.photoList.length) {
inst.photoList = null;
}
}
// Exit this whole thing early if we got nothing back.
if (photos.length == 0) {
aListener.onSearchResult(createEnum(photos));
return;
}
// We sort the array to ease de-dupeing by placing duplicate numbers
// next to each other.
owners = owners.sort();
var ownerList = "";
var prev = 0;
for each (var owner in owners) {
if (owner == prev) {
continue;
}
if (ownerList != "") {
ownerList += ",";
}
ownerList += owner;
prev = owner;
}
if (ownerList != "") {
var detailsListener = {
onSuccess: function listener_onSuccess(aEnum) {
var ownersObject = {};
aEnum = aEnum.QueryInterface(CI.nsISimpleEnumerator);
while (aEnum.hasMoreElements()) {
var person = aEnum.getNext();
person = person.QueryInterface(CI.nsIWritablePropertyBag2);
ownersObject[person.getProperty("uid")] = person;
}
for (var j = 0; j < photos.length; j++) {
var owner = ownersObject[photos[j].userid];
owner = owner.QueryInterface(CI.nsIWritablePropertyBag2);
photos[j].username = owner.getProperty("name");
var avatar = owner.getProperty("avatar");
if (avatar == "") {
avatar = FLOCK_SNOWMAN_URL;
}
photos[j].icon = avatar;
}
photos.sort(function (a,b) {
return a.uploadDate - b.uploadDate;
});
aListener.onSearchResult(createEnum(photos));
}
};
inst.api.usersGetInfo(detailsListener,
ownerList,
["name,pic_square"]);
}
}
var fqlListener = {
onSuccess: function listener_onSuccess(aXml, aStatus) {
inst.photoList = aXml.getElementsByTagName("photo");
getPhotosDetails(aCount, 1);
},
onError: function listener_onError(aSubject, aTopic, aFlockError) {
aListener.onError(aFlockError);
}
};
var qry = "SELECT pid, owner, src_small, src_big, src, link, caption, "
+ "created FROM photo ";
if (aQuery.user) {
// Query to get all photos by person with name defined in aQuery.search.
// Could be more than one person's sets of photos.
if (aQuery.album) {
qry += "WHERE aid IN (" + aQuery.album + ")";
} else {
qry += "WHERE aid IN ("
+ "SELECT aid FROM album WHERE owner = '"
+ aQuery.user
+ "')";
}
this._logger.debug("Performing FQLQuery: " + qry);
this.api.doFQLQuery(fqlListener, qry);
return;
} else if (aQuery.searchunique) {
// Query to get all photos tagged with uid specified in aQuery.searchunique
qry += "WHERE pid IN ("
+ "SELECT pid FROM photo_tag WHERE subject = '"
+ aQuery.searchunique
+ "')";
this._logger.debug("Performing FQLQuery: " + qry);
this.api.doFQLQuery(fqlListener, qry);
return;
} else if (aQuery.special) {
switch (aQuery.special) {
case "photosFromFriends":
qry += "WHERE aid IN ("
+ "SELECT aid FROM album WHERE owner IN ("
+ "SELECT uid2 FROM friend WHERE uid1 = "
+ this.api.uid
+ ")) "
+ "AND created > now() - 2592000"; // One month (in seconds)
break;
case "photosOfFriends":
qry += "WHERE pid IN ("
+ "SELECT pid FROM photo_tag WHERE subject IN ("
+ "SELECT uid2 FROM friend WHERE uid1 = "
+ this.api.uid
+ ")) "
+ "AND created > now() - 2592000"; // One month (in seconds)
break;
case "photosOfYou":
qry += "WHERE pid IN ("
+ "SELECT pid FROM photo_tag WHERE subject="
+ this.api.uid
+ ")";
break;
}
this._logger.debug("Performing FQLQuery: " + qry);
this.api.doFQLQuery(fqlListener, qry);
return;
} else {
// if user enters a search with illegal characters
// (any non alpha-num or space)
// then return right away with no results
if (aQuery.search.match(/[^a-zA-Z0-9 ]/)) {
aListener.onSearchResult(createEnum([]));
return;
}
}
function _normalizeQuery(input) {
var output;
var in_quotes = false;
// strip leading spaces
while (input.length && input[0] == " ") {
// chop off the first character
input = input.substr(1);
}
// Convert to lowercase for the wildcard search
input = input.toLowerCase();
// add leading plus
output = "+";
while (input.length) {
if (input[0] == "\"") {
if (in_quotes) {
in_quotes = false;
} else {
in_quotes = true;
}
}
// escape special chars
if ("&|!(){}[]^~*?:\\".indexOf(input[0]) >= 0) {
output = output + "\\";
}
output = output + input[0];
if (!in_quotes && input[0] == " " && input.length > 1) {
// put a plus before every word
output = output + "+";
}
input = input.substr(1);
}
// remove trailing spaces
var removed_spaces = false;
while (output.length > 0 && output[output.length - 1] == " ") {
output = output.substr(0, output.length - 1);
removed_spaces = true;
}
// if we're in a quoted string we should close that
if (in_quotes) {
output = output + "\"";
}
// if theres an incomplete word
if ((!removed_spaces) && (output[output.length - 1] != "\"")) {
// search for partial matches
output = output + "*";
}
return output;
}
// Query to get all photos tagged with name defined in aQuery.search.
// We use Lucene to get partial matches.
var searchService = CC["@flock.com/lucene/flockLucene;1"]
.getService(CI.flockILucene);
var inst = this;
var searchListener = {
onSearchComplete:
function searchListener_onSearchComplete(aNumResults, aResults) {
inst._logger.debug("onSearchComplete: " + aNumResults);
var uidArray = [];
while (aResults.hasMoreElements()) {
var result = aResults.getNext().QueryInterface(CI.flockILuceneResult);
var uid = result.URI.split(":").pop();
uidArray.push(uid);
}
var uidArrayStr = "(" + uidArray.join(",") + ")";
var fullnameQuery = "(SELECT uid "
+ "FROM user "
+ "WHERE name = '"
+ aQuery.search
+ "')"
qry += "WHERE pid IN ("
+ "SELECT pid FROM photo_tag "
+ "WHERE subject IN " + fullnameQuery + " "
+ "OR subject IN " + uidArrayStr
+ ")";
inst._logger.debug("Performing FQLQuery: " + qry);
inst.api.doFQLQuery(fqlListener, qry);
}
}
searchService.search(_normalizeQuery(aQuery.search),
"identity",
100, searchListener);
}
/**
* supportsFeature
* @see flockIMediaWebService#supportsFeature
*/
flockFacebookService.prototype.supportsFeature =
function flockFacebookService_supportsFeature(aFeature) {
var supports = {
albumCreation: true,
contacts: true,
filename: false,
privacy: false,
tags: false,
notes: true,
title: false
};
return supports[aFeature];
}
/**
* supportsSearch
* @see flockIMediaWebService#supportsSearch
*/
flockFacebookService.prototype.supportsSearch =
function flockFacebookService_supportsSearch(aQueryString) {
return false;
}
/**
* upload2
* @see flockIMediaWebService#upload
*/
flockFacebookService.prototype.upload =
function flockFacebookService_upload(aListener, aUpload, aFilename) {
var params = CC[HASH_PROPERTY_BAG_CONTRACTID]
.createInstance(CI.nsIWritablePropertyBag2);
params.setPropertyAsAString("description", aUpload.description);
this.api.uploadPhotosViaUploader(aListener, aFilename, params, aUpload);
}
/**
* The following methods defined in flockIMediaWebService are handled by
* component toolkit:
*
* @see flockIMediaWebService#checkIsStreamUrl
* @see flockIMediaWebService#handlesMediaStream
* @see flockIMediaWebService#getMediaQueryFromURL
*/
/**
* decorateForMedia
* @see flockIMediaWebService#decorateForMedia
*/
flockFacebookService.prototype.decorateForMedia =
function flockFacebookService_decorateForMedia(aDocument) {
this._logger.debug(".decorateForMedia(aDocument)");
var results = CC[HASH_PROPERTY_BAG_CONTRACTID]
.createInstance(CI.nsIWritablePropertyBag2);
var inst = this;
if (this.webDetective.detect("facebook", "person", aDocument, results) ||
this.webDetective.detect("facebook", "photo", aDocument, results)) {
var userId = results.getPropertyAsAString("userid");
var myListener = {
onSuccess: function listener_onSuccess(aEnum) {
aEnum = aEnum.QueryInterface(CI.nsISimpleEnumerator);
var person = aEnum.getNext()
.QueryInterface(CI.nsIWritablePropertyBag2);
var username;
try {
username = person.getProperty("name");
} catch (ex) {
username = userId;
}
var mediaArr = [];
var media = {
name: username,
query: "user:" + userId + "|username:" + username,
label: username,
favicon: inst.icon,
service: inst.shortName
}
mediaArr.push(media);
if (!aDocument._flock_decorations) {
aDocument._flock_decorations = {};
}
aDocument._flock_decorations.mediaArr = mediaArr;
inst.obs.notifyObservers(aDocument, "media", "media:update");
}
};
this.api.usersGetInfo(myListener, userId, ["name"]);
} else if (this.webDetective.detect("facebook",
"tagged",
aDocument,
results))
{
var userId = results.getPropertyAsAString("userid");
var myListener = {
onSuccess: function listener_onSuccess(aEnum) {
aEnum = aEnum.QueryInterface(CI.nsISimpleEnumerator);
var person = aEnum.getNext()
.QueryInterface(CI.nsIWritablePropertyBag2);
var username;
try {
username = person.getProperty("name");
} catch (ex) {
username = userId;
}
var label = flockGetString("photo/mediabar",
"flock.media.simpleviewing.searchunique",
[username]);
var mediaArr = [];
var media = {
name: username,
query: "searchunique:" + userId + "|username:" + username,
label: label,
favicon: inst.icon,
service: inst.shortName
}
mediaArr.push(media);
if (!aDocument._flock_decorations) {
aDocument._flock_decorations = {};
}
aDocument._flock_decorations.mediaArr = mediaArr;
inst.obs.notifyObservers(aDocument, "media", "media:update");
}
};
this.api.usersGetInfo(myListener, userId, ["name"]);
}
}
// Checks if the TEXTAREA is drag and droppable
flockFacebookService.prototype._isDnDableTextarea =
function ffs__isDnDableTextarea(aDocument, aXPath, aTextarea) {
if (aDocument && aXPath && aTextarea) {
var xpath = this.webDetective.getString("facebook", aXPath, "");
var results = aDocument.evaluate(xpath, aDocument, null,
CI.nsIDOMXPathResult.ANY_TYPE, null);
if (results && results.iterateNext() == aTextarea) {
return true;
}
}
return false;
}
// Set the focus on the Wall TEXTAREA by sending a focus event.
// This action is only required to activate the field if the user has not yet
// typed anything in the Wall space.
flockFacebookService.prototype._setFocusOnWall =
function ffs__setFocusOnWall(aXpathField, aTextarea) {
if (aTextarea && aXpathField && aXpathField == XPATH_WALLTEXT) {
var doc = aTextarea.ownerDocument;
if (doc instanceof CI.nsIDOMHTMLDocument) {
var placeholder = this.webDetective.getString("facebook",
"wallPlaceholder",
"");
// If the placeholder text is displaying, trigger the focus event to
// activate the TEXTAREA
if (aTextarea.value == aTextarea.getAttribute(placeholder)) {
var eventObj = doc.createEvent("Events");
eventObj.initEvent("focus", true, true);
aTextarea.dispatchEvent(eventObj);
}
}
}
}
// BEGIN flockIRichContentDropHandler Interface
// Handle the rich content drop
flockFacebookService.prototype.handleDrop =
function ffs_handleDrop(aFlavours, aTextarea) {
this._logger.debug(".handleDrop()");
var inst = this;
var dropCallback = function facebook_dropCallback(aFlav) {
var dataObj = {}, len = {};
aFlavours.getTransferData(aFlav, dataObj, len);
// Activate the TEXTAREA if we are dropping on the Wall
inst._setFocusOnWall(XPATH_WALLTEXT, aTextarea);
var caretPos = aTextarea.selectionEnd;
var currentValue = aTextarea.value;
// Add a trailing space so that Facebook scrapes for a valid,
// unmodified url
var nextChar = currentValue.charAt(caretPos);
var trailingSpace = ((nextChar == "") ||
(nextChar == " ") ||
(nextChar == "\n"))
? ""
: " ";
// Only add a breadcrumb if the insertion point is at the end of
// the text so that we don't duplicate breadcrumbs
var breadcrumb = (aTextarea.value.length == caretPos)
? CC[FLOCK_RDNDS_CONTRACTID]
.getService(CI.flockIRichDNDService)
.getBreadcrumb("plain")
: "";
aTextarea.value = currentValue.substring(0, caretPos)
+ dataObj.value.QueryInterface(CI.nsISupportsString)
.data.replace(/: /, ":\n")
+ trailingSpace
+ currentValue.substring(caretPos);
// Call special Facebook hack to embed content into field
_initKeyupEvent(aTextarea);
aTextarea.value += breadcrumb;
};
return this._handleTextareaDrop(CATEGORY_ENTRY_NAME, this.fbService.domains,
aTextarea, dropCallback);
}
// END flockIRichContentDropHandler interface
//**************************************
//* Facebook Account implementation
//**************************************
function flockFacebookAccount() {
this.acUtils = CC[FLOCK_ACCOUNT_UTILS_CONTRACTID]
.getService(CI.flockIAccountUtils);
this.service = CC[FACEBOOK_CONTRACTID]
.getService(CI.flockIWebService);
this.api = CC["@flock.com/api/facebook;1"]
.getService(CI.flockIFacebookAPI)
.wrappedJSObject;
this.faves_coop = CC[FLOCK_SINGLETON_CONTRACTID]
.getService(CI.flockISingleton)
.getSingleton(FAVES_COOP)
.wrappedJSObject;
this.webDetective = CC["@flock.com/web-detective;1"]
.getService(CI.flockIWebDetective);
this._ctk = {
interfaces: [
"nsISupports",
"flockIWebServiceAccount",
"flockISocialWebServiceAccount",
"flockIFacebookAccount",
"flockIMediaWebServiceAccount",
"flockIMediaUploadAccount"
]
};
getCompTK().addAllInterfaces(this);
this._logger = CC["@flock.com/logger;1"].createInstance(CI.flockILogger);
this._logger.init("facebookAccount");
}
// BEGIN flockIWebServiceAccount interface
flockFacebookAccount.prototype.urn = "";
flockFacebookAccount.prototype.username = "";
flockFacebookAccount.prototype.status = "";
flockFacebookAccount.prototype.activate =
function flockFacebookAccount_activate(aListener) {
this._logger.debug(".activate()");
var acctCoopObj = this.faves_coop.get(this.urn);
var inst = this;
this.api.authenticate({
onSuccess: function onSuccess() {
inst._logger.debug(".activate() authenticated: " + inst.api.uid);
if (aListener) {
// Get the user's full name before popping the mediabar so the stream
// name is correct.
var usersGetInfoListener = {
onSuccess: function ugil_onSuccess(aEnum, aStatus) {
aEnum.QueryInterface(CI.nsISimpleEnumerator);
if (aEnum.hasMoreElements()) {
var bag = aEnum.getNext();
bag.QueryInterface(CI.nsIPropertyBag);
acctCoopObj.avatar = bag.getProperty("avatar");
acctCoopObj.name = bag.getProperty("name");
acctCoopObj.nextRefresh = new Date(0);
acctCoopObj.isPollable = true;
acctCoopObj.isAuthenticated = true;
aListener.onSuccess(inst, "accountAuthorized");
}
},
onError: function ugil_onError() {
inst._logger.error("Error on usersGetInfo");
}
};
inst.api.usersGetInfo(usersGetInfoListener, inst.api.uid, null);
}
},
onError: function onError(aSubject, aTopic, aError) {
inst._logger.error("error on authentication: " + aError.errorString);
}
});
}
flockFacebookAccount.prototype.login =
function flockFacebookAccount_login(aListener) {
this._logger.debug(".login()");
var inst = this;
var myListener = {
onSuccess: function listener_onSuccess() {
inst.acUtils.ensureOnlyAuthenticatedAccount(inst.urn);
// force refresh on login
var pollerSvc = CC["@flock.com/poller-service;1"]
.getService(CI.flockIPollerService);
pollerSvc.forceRefresh(inst.urn);
if (aListener) {
aListener.onSuccess(null, "");
}
},
onError: function listener_onError(aFlockError) {
if (aListener) {
aListener.onError(null, null, aFlockError);
}
}
}
this.api.deauthenticate();
this.acUtils.markAllAccountsAsLoggedOut(FACEBOOK_CONTRACTID);
this.api.authenticate(myListener);
}
// END flockIWebServiceAccount interface
// BEGIN flockISocialWebServiceAccount interface
flockFacebookAccount.prototype.hasFriendActions = true;
flockFacebookAccount.prototype.isStatusSupported = true;
flockFacebookAccount.prototype.isStatusEditable = true;
flockFacebookAccount.prototype.isPostLinkSupported = true;
flockFacebookAccount.prototype.isMyMediaFavoritesSupported = false;
flockFacebookAccount.prototype.setStatus =
function flockFacebookAccount_setStatus(aStatusMessage, aListener)
{
this._logger.info(".setStatus('" + aStatusMessage + "')");
var inst = this;
var statusListener = {
onSuccess: function onSuccessHandler(aResult, aTopic) {
inst.faves_coop.get(inst.urn).statusMessage = aStatusMessage;
if (aListener) {
aListener.onSuccess(aResult, aTopic);
}
},
onError: function onErrorHandler(aError) {
if (aListener) {
aListener.onError(aError);
}
}
}
this.api.setStatus(aStatusMessage, statusListener);
}
flockFacebookAccount.prototype.getEditableStatus =
function flockFacebookAccount_getEditableStatus()
{
this._logger.info(".getEditableStatus()");
return this.faves_coop.get(this.urn).statusMessage;
}
flockFacebookAccount.prototype.formatStatusForDisplay =
function flockFacebookAccount_formatStatusForDisplay(aStatusMessage)
{
var sbs = CC["@mozilla.org/intl/stringbundle;1"]
.getService(CI.nsIStringBundleService);
var bundle = sbs.createBundle(FACEBOOK_PROPERTIES);
return bundle.GetStringFromName("flock.facebook.status.prefix") + " "
+ aStatusMessage
+ (!aStatusMessage.match(/[.!?]+$/)
? bundle.GetStringFromName("flock.facebook.status.postfix") : "");
}
flockFacebookAccount.prototype.getMeNotifications =
function flockFacebookAccount_getMeNotifications()
{
this._logger.info(".getMeNotifications()");
var sbs = CC["@mozilla.org/intl/stringbundle;1"]
.getService(CI.nsIStringBundleService);
var bundle = sbs.createBundle(FACEBOOK_PROPERTIES);
var noties = [];
var inst = this;
function _addNotie(aType, aCount) {
var stringName = "flock.facebook.noties."
+ aType + "."
+ ((parseInt(aCount) <= 0) ? "none" : "some");
noties.push({
class: aType,
tooltip: bundle.GetStringFromName(stringName),
metricsName: aType,
count: aCount,
URL: inst.webDetective.getString("facebook", aType + "_URL", "")
});
}
var c_acct = this.faves_coop.get(this.urn);
_addNotie("meMessages", c_acct.accountMessages);
_addNotie("mePokes", c_acct.fbPokes);
_addNotie("meFriendRequests", c_acct.fbFriendRequests);
_addNotie("meGroupRequests", c_acct.fbGroupInvites);
_addNotie("meEventRequests", c_acct.fbEventInvites);
return JSON.toString(noties);
}
flockFacebookAccount.prototype.markAllMeNotificationsSeen =
function flockFacebookAccount_markAllMeNotificationsSeen(aType) {
this._logger.debug(".markAllMeNotificationsSeen('" + aType + "')");
var c_acct = this.faves_coop.get(this.urn);
switch (aType) {
case "meMessages":
c_acct.accountMessages = 0;
break;
case "mePokes":
c_acct.fbPokes = 0;
break;
case "meFriendRequests":
c_acct.fbFriendRequests = 0;
break;
case "meGroupRequests":
c_acct.fbGroupInvites = 0;
break;
case "meEventRequests":
c_acct.fbEventInvites = 0;
break;
default:
break;
}
}
flockFacebookAccount.prototype.getFriendActions =
function flockFacebookAccount_getFriendActions(aFriendURN)
{
this._logger.info(".getFriendActions('" + aFriendURN + "')");
var actionNames = ["friendPoke",
"friendMessage",
"friendWallPost",
"friendShareLink",
"friendViewProfile",
"friendGiveGift",
"friendShareFlock"];
var sbs = CC["@mozilla.org/intl/stringbundle;1"]
.getService(CI.nsIStringBundleService);
var bundle = sbs.createBundle(FACEBOOK_PROPERTIES);
var actions = [];
var c_friend = this.faves_coop.get(aFriendURN);
if (c_friend) {
var c_acct = this.faves_coop.get(this.urn);
for each (var i in actionNames) {
actions.push({
label: bundle.GetStringFromName("flock.facebook.actions." + i),
class: i,
spec: this.webDetective.getString("facebook", i, "")
.replace("%accountid%", c_acct.accountId)
.replace("%friendid%", c_friend.accountId)
});
}
}
return JSON.toString(actions);
}
flockFacebookAccount.prototype.getSharingAction =
function flockFacebookAccount_getSharingAction(aFriendURN, aTransferable)
{
this._logger.info(".getSharingAction('" + aFriendURN + "'," +
"'" + aTransferable + "')");
// We want to take advantage of Facebook's "Share Link" feature so we try to
// incorporate it for every object we share.
return "method:flockIFacebookAccount:sendMessageWithSharedLink";
}
flockFacebookAccount.prototype.getPostLinkAction =
function flockFacebookAccount_getPostLinkAction(aTransferable)
{
// We need to add the breadcrumb so we'll handle this in the postLink method
return "method:flockIFacebookAccount:postLink";
}
flockFacebookAccount.prototype.getProfileURLForFriend =
function flockFacebookAccount_getProfileURLForFriend(aFriendURN)
{
this._logger.info(".getProfileURLForFriend('" + aFriendURN + "')");
var url = "";
var c_friend = this.faves_coop.get(aFriendURN);
if (c_friend) {
url = this.webDetective.getString("facebook", "friendprofile", "")
.replace("%accountid%", c_friend.accountId);
}
return url;
}
// END flockISocialWebServiceAccount interface
// BEGIN flockIFacebookAccount interface
flockFacebookAccount.prototype.sendMessageWithSharedLink =
function flockFacebookAccount_sendMessageWithSharedLink(aFriendURN,
aTransferable)
{
this._logger.info(".sendMessageWithSharedLink('" + aFriendURN + "'," +
"'" + aTransferable + "')");
var msgURL = "";
if (!aTransferable) {
// Was not a DND but a programmatical call
// Special case requires us to get the active browser tab and then use
// that as the sharing URL
var win = _wm.getMostRecentWindow("navigator:browser");
if (win) {
var currentURL = win.gBrowser.currentURI.spec;
var subject = win.gBrowser.contentTitle;
var body = currentURL;
var c_friend = this.faves_coop.get(aFriendURN);
msgURL = this._buildSendMessageURL(c_friend.accountId, subject, body);
}
} else {
msgURL = this._getSendMessageURLFromTransferable(aFriendURN,
aTransferable);
}
if (msgURL) {
this._loadNewTabWithCallback(msgURL, this._sharedLinkCallback);
}
}
flockFacebookAccount.prototype.shareFlock =
function flockFacebookAccount_shareFlock(aFriendURN)
{
this._logger.info(".shareFlock('" + aFriendURN + "')");
// Get friend
var c_friend = this.faves_coop.get(aFriendURN);
if (c_friend) {
// Get "Share Flock" message text
var sbs = CC["@mozilla.org/intl/stringbundle;1"]
.getService(CI.nsIStringBundleService);
var bundle = sbs.createBundle(FACEBOOK_PROPERTIES);
var subject = bundle.GetStringFromName(FACEBOOK_SHARE_FLOCK_SUBJECT);
try {
var body = bundle.GetStringFromName(FACEBOOK_SHARE_FLOCK_MESSAGE + "0");
for (var i = 1; ; i++) {
body += "\n" + bundle.GetStringFromName(FACEBOOK_SHARE_FLOCK_MESSAGE + i);
}
} catch (ex) {
// Ignore -- we've hit the end of our message lines
}
// Build the URL and send it to the friend
var msgURL = this._buildSendMessageURL(c_friend.accountId, subject, body);
this._loadNewTabWithCallback(msgURL, this._shareFlockCallback);
}
}
flockFacebookAccount.prototype.postLink =
function flockFacebookAccount_postLink(aTransferable)
{
var url;
if (aTransferable) {
// Something was dropped onto the "Post Link" button: get the URL from the
// transferable
// This flavor set will ignore a text selection, which is OK
var flavors = ["text/x-flock-media",
"text/x-moz-url"];
var message = CC[FLOCK_RDNDS_CONTRACTID]
.getService(CI.flockIRichDNDService)
.getMessageFromTransferable(aTransferable,
flavors.length,
flavors);
url = message.body;
} else {
// The "Post Link" button was clicked: get the current tab's URL
var win = _wm.getMostRecentWindow("navigator:browser");
if (win) {
url = win.gBrowser.currentURI.spec;
}
}
if (url) {
// Post it onto this user's profile
var postURL = this.webDetective.getString("facebook", "postLink", "")
.replace("%url%", encodeURIComponent(url));
if (postURL) {
this._loadNewTabWithCallback(postURL, this._postLinkCallback);
}
}
}
flockFacebookAccount.prototype._getSendMessageURLFromTransferable =
function flockFacebookAccount__getSendMessageURLFromTransferable(aFriendURN,
aTransferable)
{
this._logger.info("._getSendMessageURLFromTransferable('" + aFriendURN + "'," +
"'" + aTransferable + "')");
var messageURL = "";
var c_friend = this.faves_coop.get(aFriendURN);
if (c_friend) {
// Flavors we want to support, in order of preference
var flavors = ["text/x-flock-media",
"text/x-moz-url",
"text/unicode",
"text/html"];
var message = CC[FLOCK_RDNDS_CONTRACTID]
.getService(CI.flockIRichDNDService)
.getMessageFromTransferable(aTransferable,
flavors.length,
flavors);
if (message.body) {
messageURL = this._buildSendMessageURL(c_friend.accountId,
message.subject,
message.body);
}
}
return messageURL;
}
flockFacebookAccount.prototype._buildSendMessageURL =
function flockFacebookAccount__buildSendMessageURL(aFriendId, aSubject, aBody) {
return this.webDetective.getString("facebook", "sendMessage", "")
.replace("%friendid%", aFriendId)
.replace("%subject%", encodeURIComponent(aSubject))
.replace("%message%", encodeURIComponent(aBody));
}
// This opens a new tab and loads aURL into it. After the tab has loaded, the
// aCallback method is called.
flockFacebookAccount.prototype._loadNewTabWithCallback =
function flockFacebookAccount__loadNewTabWithCallback(aUrl, aCallback)
{
var win = _wm.getMostRecentWindow("navigator:browser");
if (win) {
var browser = win.getBrowser();
var newTab = browser.loadOneTab(aUrl, null, null, null, false, false);
var obs = CC["@mozilla.org/observer-service;1"]
.getService(CI.nsIObserverService);
var observer = {
observe: function loadNewTabWithCallback_observer(aContent,
aTopic,
aOwnsWeak)
{
var contentWindow = newTab.linkedBrowser.docShell
.QueryInterface(CI.nsIInterfaceRequestor)
.getInterface(CI.nsIDOMWindow);
if (contentWindow == aContent) {
obs.removeObserver(this, aTopic);
// Callback?
if (aCallback) {
aCallback(contentWindow);
}
}
}
}
obs.addObserver(observer, "EndDocumentLoad", false);
}
}
// Callbacks for _loadNewTabWithCallback
// Adds the breadcrumb to the post link comment field.
flockFacebookAccount.prototype._postLinkCallback =
function flockFacebookAccount__postLinkCallback(aWindow) {
// TEXTAREA field on post link page is called "sharer_popup_message"
var formField = aWindow.document.getElementById("sharer_popup_message");
if (formField) {
// Add breadcrumb
_addBreadcrumb(formField);
// Put the cursor at the top of the text area, above breadcrumb
formField.setSelectionRange(0, 0);
}
}
// Embeds the shared link into the content of the message and then adds the
// breadcrumb.
flockFacebookAccount.prototype._sharedLinkCallback =
function flockFacebookAccount__sharedLinkCallback(aWindow) {
// TEXTAREA field on send message page is called "message"
var formField = aWindow.document.getElementById("message");
if (formField) {
// Call special Facebook hack to embed content into field
_initKeyupEvent(formField);
// Add breadcrumb
_addBreadcrumb(formField);
}
}
// Embeds the Flock link into the content of the message, cleans up the text,
// and then moves the cursor to the top of the message.
flockFacebookAccount.prototype._shareFlockCallback =
function flockFacebookAccount__shareFlockCallback(aWindow) {
// TEXTAREA field on send message page is called "message"
var formField = aWindow.document.getElementById("message");
if (formField) {
// Call special Facebook hack to embed content into field
_initKeyupEvent(formField);
// Facebook converts newlines on the command line to breaks in the message,
// which looks bad, so convert them back to newlines in the message.
formField.value = formField.value.replace("<br />", "\n", "gi");
// Put the cursor at the top of the text area, above our footer
formField.setSelectionRange(0, 0);
}
}
// END flockIFacebookAccount interface
//******************************************
//* XPCOM registration
//******************************************
function createModule(aParams) {
return {
registerSelf: function registerSelf(aCompMgr,
aFileSpec,
aLocation,
aType) {
aCompMgr.QueryInterface(CI.nsIComponentRegistrar);
aCompMgr.registerFactoryLocation(aParams.CID,
aParams.componentName,
aParams.contractID,
aFileSpec,
aLocation,
aType);
var catMgr = CC[CATEGORY_MANAGER_CONTRACTID].
getService(CI.nsICategoryManager);
if (!aParams.categories) {
aParams.categories = [];
}
for (var i = 0; i < aParams.categories.length; i++) {
var cat = aParams.categories[i];
catMgr.addCategoryEntry(cat.category,
cat.entry,
cat.value,
true,
true);
}
},
getClassObject: function getClassObject(aCompMgr, aCID, aIID) {
if (!aCID.equals(aParams.CID)) {
throw CR.NS_ERROR_NO_INTERFACE;
}
if (!aIID.equals(CI.nsIFactory)) {
throw CR.NS_ERROR_NOT_IMPLEMENTED;
}
return { // Factory
createInstance: function createInstance(aOuter, aIID) {
if (aOuter != null) {
throw CR.NS_ERROR_NO_AGGREGATION;
}
var comp = new aParams.componentClass();
if (aParams.implementationFunc) {
aParams.implementationFunc(comp);
}
return comp.QueryInterface(aIID);
}
};
},
canUnload: function canUnload(aCompMgr) {
return true;
}
};
}
// NS Module entrypoint
function NSGetModule(aCompMgr, aFileSpec) {
return createModule({
componentClass: flockFacebookService,
CID: FACEBOOK_CID,
contractID: FACEBOOK_CONTRACTID,
componentName: CATEGORY_COMPONENT_NAME,
implementationFunc: function implementationFunc(aComp) {
getCompTK().addAllInterfaces(aComp);
},
categories: [
{
category: "wsm-startup",
entry: CATEGORY_COMPONENT_NAME,
value: FACEBOOK_CONTRACTID
},
{
category: "flockWebService",
entry: CATEGORY_ENTRY_NAME,
value: FACEBOOK_CONTRACTID
},
{
category: "flockMediaProvider",
entry: CATEGORY_ENTRY_NAME,
value: FACEBOOK_CONTRACTID
},
{
category: FLOCK_RICH_CONTENT_CATEGORY_ENTRY,
entry: CATEGORY_ENTRY_NAME,
value: FACEBOOK_CONTRACTID
}
]
});
}